﻿using System;

namespace DSAManager.Utils
{
    public class IdWorker
    {
        private long lastTimestamp = -1L;
        private long sequenceBits = 10L;
        private long sequence = 0L;
        public long sequenceMask;
        private static long twepoch = 641897085599161712L;//1422930838828L;//1288834974657L;//1361753741828L;
        private long workerIdBits = 4L;
        private long timestampLeftShift;
        private long workerId;
        private long workerIdShift;
        public long maxWorkerId;
        private readonly object syncLock = new object();


        private static IdWorker _instance;

        public static IdWorker Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new IdWorker(1);
                return _instance;
            }
        }

        public static long GenId()
        {
            long workid = new Random().Next(1, 100);
            IdWorker worker=new IdWorker(1);
            return worker.nextId();
        }


        public IdWorker(long workerId)
        {
            workerIdShift = sequenceBits;
            maxWorkerId = -1L ^ -1L << Convert.ToInt32(workerIdBits);
            timestampLeftShift = sequenceBits + Convert.ToInt32(workerIdBits);
            sequenceMask = -1L ^ -1L << Convert.ToInt32(sequenceBits);

            if (workerId > this.maxWorkerId || workerId < 0)
            {
                //throw new IllegalArgumentException(String.format(
                //  "worker Id can't be greater than %d or less than 0",
                //  this.maxWorkerId));
            }
            this.workerId = workerId;
        }

        public long nextId()
        {
            lock (syncLock)
            {
                var t = DateTime.Now.AddYears(20).Ticks;

                long timestamp = this.timeGen();
                if (this.lastTimestamp == timestamp)
                {
                    this.sequence = (this.sequence + 1) & this.sequenceMask;
                    if (this.sequence == 0)
                    {
                        timestamp = this.tilNextMillis(this.lastTimestamp);
                    }
                }
                else
                {
                    this.sequence = 0;
                }
                if (timestamp < this.lastTimestamp)
                {
                    throw new ArgumentException("Clock moved backwards.");
                }
                this.lastTimestamp = timestamp;
                long nextId = ((timestamp - twepoch << Convert.ToInt32(timestampLeftShift)))
                              | (this.workerId << Convert.ToInt32(this.workerIdShift)) | (this.sequence);
                return nextId;
            }
        }


        private long tilNextMillis(long lastTimestamp)
        {
            long timestamp = this.timeGen();
            while (timestamp <= lastTimestamp)
            {
                timestamp = this.timeGen();
            }
            return timestamp;
        }

        private long timeGen()
        {
            return DateTime.Now.Ticks;
        }
    }
}
